/*
 * {copyright later}
 *
 * @author (C) <a href="http://www.carfield.com.hk">Carfield Yim </a>, 2004
 */
package hk.com.carfield.web.servlet;

import hk.com.carfield.action.Body;
import hk.com.carfield.action.Plain;
import hk.com.carfield.core.ActionManager;
import hk.com.carfield.core.Context;
import hk.com.carfield.persistance.GenericEntity;
import hk.com.carfield.util.FileUtil;
import hk.com.carfield.util.LangUtil;
import hk.com.carfield.util.LogUtil;
import hk.com.carfield.util.StringUtil;
import hk.com.carfield.util.UrlUtil;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;

import ar.com.pabloa.jmimetypelib.JMimetypeLib;

public final class Controller extends HttpServlet {
	private static final long serialVersionUID = 1L;

	private Context context;

	private final JMimetypeLib mimetypeLib = new JMimetypeLib();

	@Override
	public void destroy() {
		super.destroy();
		context.shutdown();
	}

	@Override
	public void doGet(final HttpServletRequest request,
			final HttpServletResponse response) throws IOException {
		doRequest(request, response);
	}

	@Override
	public void doPost(final HttpServletRequest request,
			final HttpServletResponse response) throws IOException {
		// post request must be processed by admin action!
		request.setAttribute("wrapped", "true");
		doRequest(request, response);
	}

	private void doRequest(final HttpServletRequest req,
			final HttpServletResponse res) throws IOException {
		final hk.com.carfield.web.Request request = Request.getRequest(req,
				context.config);
		File location = context.config.webRoot;
		try {
			final Body body;
			try {
				if (context.config.disallow(request.clientIP())) {
					return;
				}

				location = UrlUtil.getLocation(context,
						request.getParameter("url"), req.getRequestURI());
				res.setContentType("text/html; charset=UTF-8");

				context.executeTasks(request, location);
				if (FileUtil.notFound(location)) {
					final InputStream is = UrlUtil.resolveInputStream(
							context.config, location.getName());
					if (is != null) {
						redirect(res, location, is);
						return;
					}
					body = Plain.notFoundBody(location, context);
					res.setStatus(HttpServletResponse.SC_NOT_FOUND);
				} else if (context.config.isSecure(location, request)) {
					body = Plain.forbiddenBody(location, context);
					res.setStatus(HttpServletResponse.SC_FORBIDDEN);
				} else if (request.getBoolean("downloadZip")) {
					res.setContentType("application/zip");
					res.setHeader("Content-Disposition",
							"attachment; filename=\"" + location.getName()
									+ ".zip");
					final boolean result = FileUtil.zip(res.getOutputStream(),
							location);
					if (result) {
						return;
					} else {
						body = Plain.getErrorBody(new FileNotFoundException(
								"Not file under this location can be zip"),
								location, context, request);
					}
				} else if (StringUtil.notBlank(request
						.getParameter("actioncode"))
						|| request.getBoolean("wrapped")
						|| location.isDirectory()) {
					body = ActionManager.getBody(context, request, location);
				} else if (request.getBoolean("thumbnail")) {
					redirect(res, location, new ByteArrayInputStream(
							GenericEntity.getByte(context, location)));
					return;
				} else {
					redirect(res, location);
					return;
				}
			} catch (final RuntimeException e) {
				LogUtil.error(e, context.mailMan, location, request);
				res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
				context.templateMan.mergeTemplate(context,
						Plain.getErrorBody(e, location, context, request),
						location, request, res.getCharacterEncoding(),
						res.getOutputStream());
				return;
			}
			if (body.getRedirectTo() != null) {
				res.sendRedirect(body.getRedirectTo());
				return;
			} else if (request.getBoolean("xml")) {
				StringUtil.writeXML(request.host(), body.getHead(),
						res.getWriter());
			} else {
				context.templateMan.mergeTemplate(context, body, location,
						request, res.getCharacterEncoding(),
						res.getOutputStream());
			}
		} catch (final Throwable e) {
			LogUtil.error(e, context.mailMan, location, request);
			LangUtil.exitForOOME(e);
			res.getWriter()
					.println(
							"System error, admin are noted and please visit again tomorrow");
		}
	}

	@Override
	protected final long getLastModified(final HttpServletRequest req) {
		final hk.com.carfield.web.Request request = Request.getRequest(req,
				context.config);
		final File location = UrlUtil.getLocation(context,
				request.getParameter("url"), req.getRequestURI());
		return FileUtil.cacheExpire(location);
	}

	@Override
	public void init(final ServletConfig servletConfig) throws ServletException {
		super.init(servletConfig);
		final String root = servletConfig.getServletContext().getRealPath("/");
		String dbName = servletConfig.getInitParameter("dbName");
		dbName = dbName == null ? "db" : dbName;
		try {
			context = new Context(new File(root + "/WEB-INF/data/"), dbName);
		} catch (final Exception e) {
			throw new RuntimeException(e);
		}
	}

	private void redirect(final HttpServletResponse res, final File location)
			throws IOException, FileNotFoundException {
		// TODO set charset with mozilla library
		if (location.isFile()) {
			final InputStream is = new FileInputStream(location);
			redirect(res, location, is);
		} else {
			LogUtil.error(
					new IOException("Try to show raw of invalid location"),
					context.mailMan, location, null);
		}
	}

	private void redirect(final HttpServletResponse res, final File file,
			final InputStream is) throws IOException {
		try {
			res.setContentType(mimetypeLib.getMimeType(file));
			setCacheDate(res, file.lastModified());
			IOUtils.copy(is, res.getOutputStream());
		} catch (final RuntimeException e) {
			// ignore
		} finally {
			is.close();
		}
	}

	private void setCacheDate(final HttpServletResponse res, final long ts) {
		res.setDateHeader("last-modified", ts);
		res.setDateHeader("if-modified-since", ts);
	}

}
